Skip to content

Why the Dance?

Video Summary

So, this is a meme that has been circulating on social media, in developer communities:

It shows TikTok sensation Khaby Lame. His whole schtick is that he shows how things are over-engineered and can be greatly simplified.

The joke here is that state in React is so complicated, while in Svelte (an alternative front-end framework, represented by that "S" in the corner), it's much more straightforward.

In this video, we look at why React does things the way that it does, why it can't do it the way Svelte does it, and why that might not necessarily be such a bad thing.

Let's start by looking at a typical React setup:

function App() {
const [count, setCount] = React.useState(0);
return (
<>
<p>You've clicked {count} times.</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click me!
</button>
</>
);
}

Just for fun, let's try and do this the Svelte way. Let's convert this code:

function App() {
let count = 0;
return (
<>
<p>You've clicked {count} times.</p>
<button
onClick={() => {
count = count + 1;
}}
>
Click me!
</button>
</>
);
}

Looks way nicer, but unfortunately, it doesn't work.

But why not? Svelte does it, right?

Well, here's the thing: JavaScript doesn't give us a way to “observe” variables. When we reassign the count variable from 0 to 1, nothing happens as a result.

The only way to update the UI in React is to trigger a re-render. And because we can't observe variables, React has no way of knowing that changing the count variable should trigger a re-render!

It works in Svelte because Svelte isn't JavaScript. It's a custom language that compiles to JavaScript. The code that you write in Svelte is simpler, but it's also a lot more magical; a compiler will turn it into something just as complex as React, but it's hidden behind a curtain, veiled from view.

Now, let's say that we have a magic wand, and we can change the React API however we want. Maybe we can do something like this:

import React from 'react';
function App() {
let count = 0;
return (
<>
<p>You've clicked {count} times.</p>
<button
onClick={() => {
count = count + 1;
React.reRender();
}}
>
Click me!
</button>
</>
);
}

We change the count variable, and then we explicitly tell React to re-render with a made-up React.reRender() function call.

Well, even if this method existed, it still wouldn't work. 😬

To understand why, let's step outside of React, and consider this from a vanilla JS perspective:

function helloWorld() {
let count = 0;
count = count + 1;
}

When we call this helloWorld() function, we initialize a count variable, but it's scoped to this helloWorld function. We then increment it, and then the function wraps up.

Suppose we call this function multiple times in a row:

function helloWorld() {
let count = 0;
count = count + 1;
}
helloWorld();
helloWorld();
helloWorld();
helloWorld();

In each case, count will be re-initialized to 0, and incremented to 1. It doesn't “remember” the value from the previous function call!

Now, let's look again at our React example:

import React from 'react';
function App() {
let count = 0;
return (
<>
<p>You've clicked {count} times.</p>
<button
onClick={() => {
count = count + 1;
React.reRender();
}}
>
Click me!
</button>
</>
);
}

When we “render a component” in React, we invoke the function. This means that whenever App renders, it runs all this code again. We create a new count variable which is initialized to 0. It's the same problem!

What we would need to do is to save the value outside this component, somewhere inside React. Then, we could pluck it out. Maybe something like this:

import React from 'react';
function App() {
let count = getValueForState({ initialValue: 0 });
return (
<>
<p>You've clicked {count} times.</p>
<button
onClick={() => {
React.reRender(count + 1);
}}
>
Click me!
</button>
</>
);
}

In fact, this is pretty much what the useState hook does!

Let's look at the original code again:

function App() {
const [count, setCount] = React.useState(0);
return (
<>
<p>You've clicked {count} times.</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click me!
</button>
</>
);
}

We create a count state variable with the useState function. This value lives on the component instance, something we'll learn about shortly.

This function gives us the current value of count, as well as a setCount function. This function has two purposes:

  1. It allows us to change the value of count. This is the main thing we think of.
  2. It signals to React that a re-render is required, like our hypothetical React.reRender() method!

When we think about it, this is actually a really elegant way to solve the problem.

And so, the React way is certainly a lot more verbose than the Svelte way, but it's also a lot more “real”. The complexity is up-front, rather than being swept under the rug.

This isn't a knock on Svelte. Honestly, Svelte is great! But this is a significant trade-off, and it isn't clear to me that one approach is objectively better than the other. Personally, I prefer to understand what's going on.

At the start of my career, I was a back-end developer who used Ruby on Rails. If you're not familiar, Ruby on Rails is a Ruby framework that relies heavily on magic. You write a tiny amount of code, and a bunch of invisible gremlins kick into gear, and do a bunch of cool stuff for you.

In the best case, this is wonderful. You can move super quickly… at first. But eventually, you run into a misalignment between how you think it works, and how it actually works. Or, maybe you do something in a slightly unconventional way. Or, you hit some niche bug in the framework.

When one of these things happens, you're suddenly lost in the woods. Because you don't actually understand what's going on, it's nearly impossible to fix the problem. I've spent so many days in the trenches with Ruby on Rails, just trying to get the unstuck.

Screenshot from the “Lost Woods” from Super Mario World

The React way has a steeper learning curve and it can feel a lot more high-friction, but now that I understand it, I never feel like I get lost in the woods. And that's part of what makes React so nice to use.